'use strict';

const os       = require('os');
const fs       = require('fs');
const del      = require('del');
const path     = require('path');
const moment   = require('moment');
const webpack  = require('webpack');
const mkdirp   = require('mkdirp');
const readline = require('readline');
const es       = require("event-stream");
const spawn    = require('child_process').exec;

const gulp       = require('gulp');
const git        = require('gulp-git');
const rev        = require('gulp-rev');
const util       = require('gulp-util');
const exec       = require('gulp-exec');
const less       = require('gulp-less');
const debug      = require('gulp-debug');
const order      = require('gulp-order');
const change     = require('gulp-change');
const watch      = require('gulp-watch');
const concat     = require('gulp-concat');
const uglify     = require('gulp-uglify');
const inject     = require('gulp-inject');
const htmlmin    = require('gulp-htmlmin');
const replace    = require('gulp-replace');
const cleancss   = require('gulp-clean-css');
const ts         = require('gulp-typescript');
const sourcemaps = require('gulp-sourcemaps');
const liveserver = require('gulp-live-server');
const stripdebug = require('gulp-strip-debug');

// const webpackConfig = require('./webpack.config.js');

require('events').EventEmitter.prototype._maxListeners = 35;

const tsFiles  = ['./src/**/*.ts', './src/tsconfig.json'];
const uglifier = {
    mangle  : true,
    compress: {
        sequences    : true,  // join consecutive statemets with the “comma operator”
        properties   : true,  // optimize property access: a["foo"] → a.foo
        dead_code    : true,  // discard unreachable code
        drop_debugger: true,  // discard “debugger” statements
        conditionals : true,  // optimize if-s and conditional expressions
        comparisons  : true,  // optimize comparisons
        evaluate     : true,  // evaluate constant expressions
        booleans     : true,  // optimize boolean expressions
        loops        : true,  // optimize loops
        // unused       : true,  // drop unused variables/functions
        if_return    : true,  // optimize if-s followed by return/continue
        join_vars    : true,  // join var declarations
        cascade      : true,  // try to cascade `right` into `left` in sequences
        side_effects : true,  // drop side-effect-free statements
        // warnings     : true,  // warn about potentially dangerous optimizations/code
        global_defs  : {}     // global definitions
    }
};

const distDir = 'dist';
const outsDir = 'build';
const htmlCfg = {
    removeComments         : true,
    removeCommentsFromCDATA: true
};

const serverEnv = {
    cwd         : outsDir,
    DEBUG       : 'AppInOne:*',
    DEBUG_FD    : 1,
    DEBUG_COLORS: 'yes',
    NODE_ENV    : 'development',
    PORT        : process.env.PORT || 8000,
    UPLOADS     : process.env.UPLOADS || './uploads'
};

const testerEnv = {
    cwd         : outsDir,
    DEBUG       : 'AppInOne:*',
    DEBUG_FD    : 1,
    DEBUG_COLORS: 'yes',
    NODE_ENV    : 'product',
    PORT        : process.env.PORT || 3000,
    UPLOADS     : process.env.UPLOADS || './uploads'
};

const deployRepos = {
    hc360  : { src: 'ppe@10.8.81.31:/home/ppe/repos/web', key: 'hc360' },
    test   : { src: 'ppe@192.168.1.223:/home/ppe/repos/web', key: 'ppet' },
    trainer: { src: 'ppe@192.168.1.224:/home/ppe/repos/web', key: 'trainer' },
    aliyun : { src: 'ppe@121.41.28.30:/home/ppe/repos/web', key: 'aliyun' },
    product: { src: 'ppe@10.8.81.31:/home/ppe/repos/web', key: 'hc360' }
};

let isWinOS     = os.platform() === 'win32';
let tsProject   = ts.createProject('tsconfig.json');
let tsConfig    = tsProject.config.compilerOptions;
let server      = liveserver(`${outsDir}/main.js`, { env: serverEnv });
let tester      = liveserver(`${distDir}/main.js`, { env: testerEnv });
let isProdBuild = process.argv[2] === 'product';

function clearTerm() {
    let stdout = "";
    if( isWinOS === false ) {
        stdout += "\x1B[2J";
    } else {
        let lines = process.stdout.getWindowSize()[1];
        for( let i = 0; i < lines; i++ ) {
            stdout += "\r\n";
        }
    }
    stdout += "\x1B[0f";
    process.stdout.write(stdout);
}

function gitExec(args, cwd, quiet) {
    return new Promise((resovle, reject)=> {
        git.exec({ args: args, cwd: cwd, quiet: quiet }, function(err, stdout) {
            if( err ) {
                reject(err);
                return;
            }
            resovle(stdout);
        });
    });
}

// replace everything before last '/' width '**'
function sortMask(files) {
    let arr = [];
    files.forEach((e) => {
        arr.push('**/' + e.substr(e.lastIndexOf('/')));
    });
    return arr;
}

// ------------------------- devel steps ---------------------------------

gulp.task('fixtype', ['clean'], () => {
    let modifications = [
        {
            file        : 'typings/globals/ejs/index.d.ts',
            replacements: [
                {
                    from: 'var cache: Cache;',
                    to  : 'var cache: Cache;\n\t\tvar __express: any;'
                }
            ]
        },
        {
            file        : 'typings/globals/body-parser/index.d.ts',
            replacements: [
                {
                    from: 'export function urlencoded(options: {',
                    to  : 'export function urlencoded(options: {\n\t\t\tparameterLimit?: number;'
                }
            ]
        },
        {
            file        : 'typings/globals/connect-mongo/index.d.ts',
            replacements: [
                {
                    from: 'export interface MongoStoreFactory {',
                    to  : 'export interface MongoStoreFactory {\n\t\t\t' +
                          'new (options: {url: string, touchAfter?: number, collection?: string}): MongoStore;'
                }
            ]
        },
        {
            file        : 'typings/globals/nodemailer/index.d.ts',
            replacements: [
                {
                    from: 'Transport = nodemailer.Transport',
                    to  : 'Transport = any'
                },
                {
                    from: 'SendMailOptions = nodemailer.SendMailOptions',
                    to  : 'SendMailOptions = any'
                },
                {
                    from: 'SentMessageInfo = nodemailer.SentMessageInfo',
                    to  : 'SentMessageInfo = any'
                }
            ]
        },
        {
            file        : 'typings/globals/nodemailer-direct-transport/index.d.ts',
            replacements: [
                {
                    from: 'directTransport(options: directTransport.DirectOptions): nodemailer.Transport',
                    to  : 'directTransport(options: directTransport.DirectOptions): any'
                }
            ]
        },
        {
            file        : 'typings/globals/nodemailer-smtp-transport/index.d.ts',
            replacements: [
                {
                    from: 'smtpTransport(options: smtpTransport.SmtpOptions): nodemailer.Transport',
                    to  : 'smtpTransport(options: smtpTransport.SmtpOptions): any'
                }
            ]
        }
    ];
    modifications.forEach(function(m) {
        try {
            let str = fs.readFileSync(m.file).toString();
            m.replacements.forEach(function(r) {
                if( str.indexOf(r.to) == -1 ) {
                    str = str.replace(r.from, r.to);
                }
            });
            fs.writeFileSync(m.file, str);
        } catch( e ) {
            console.log(e);
        }
    });
});

gulp.task('links', ['clean'], () => {
    let dirs = [];

    mkdirp.sync(`./${outsDir}`);
    dirs.push('./package.json');
    dirs.push('./node_modules');
    dirs.forEach((e) => {
        let src  = path.join(__dirname, e);
        let dest = path.join(__dirname, e.replace(/\.(\/src)?/, outsDir));
        try {
            fs.accessSync(dest);
        } catch( e ) {
            fs.symlinkSync(src, dest);
        }
    });
    let staticsDirs = [];

    staticsDirs.push('./src/frontend/statics');

    staticsDirs.forEach((e) => {
        let src  = path.join(__dirname, e);
        let dest = path.join(__dirname, e.replace(/\.(\/src\/frontend)?/, outsDir));
        try {
            fs.accessSync(dest);
        } catch( e ) {
            fs.symlinkSync(src, dest);
        }
    });
});

gulp.task('prepare', ['fixtype', 'links']);

gulp.task('clean', () => {
    let dirs      = [
        `${outsDir}`,
        `${distDir}/frontend`,
        `${distDir}/config`,
        `${distDir}/models`,
        `${distDir}/actions`,
        `${distDir}/core`
    ];
    let files     = [`${distDir}/*.js`, `${distDir}/*.json`];
    let cmdRmFile = isWinOS ? 'del /F /Q <%= file.path %>' : 'rm -f <%= file.path %>';
    let cmdRmDir  = isWinOS ? 'rmdir /S /Q <%= file.path %>' : 'rm -rfd <%= file.path %>';

    return es.merge(gulp.src(dirs).pipe(exec(cmdRmDir)), gulp.src(files).pipe(exec(cmdRmFile)));
});

gulp.task('tsc', ['links'], () => {
    // clearTerm();
    console.log('---------------------- TS compiling ----------------------');
    return tsProject.src()
                    .pipe(sourcemaps.init())
                    .pipe(ts(tsProject))
                    .js
                    .pipe(sourcemaps.write('.', { sourceRoot: (file) => file.cwd + '/src' }))
                    .pipe(replace('"use strict";', "'use strict';\nrequire('source-map-support/register');"))
                    .pipe(gulp.dest(`./${outsDir}/`));
});

// ------------------------- build steps ---------------------------------

gulp.task('appmin', ['tsc'], () => {
    return gulp.src([`./${outsDir}/**/*.js`, `!./${outsDir}/statics/**/*.js`])
               .pipe(replace("require('source-map-support/register');", ''))
               .pipe(uglify(uglifier))
               .pipe(gulp.dest(`./${distDir}`));
});

// ------------------------- css minify  ---------------------------------
gulp.task('cssminHome', ['less'], () => {
    let main  = ['css/home/index.css'];
    let home  = ['css/home/**/*.css', '!css/home/index.css'];
    let smain = gulp.src(main, { cwd: `./${outsDir}/statics/` })
                    .pipe(order(sortMask(main)))
                    .pipe(concat('main'));

    let shome = gulp.src(home, { cwd: `./${outsDir}/statics/` })
                    .pipe(concat('home'));

    let ss = es.concat([smain, shome])
               .pipe(order(['main', 'home']))
               .pipe(concat('home.css'))
               .pipe(cleancss())
               .pipe(rev())
               .pipe(gulp.dest(`./${distDir}/statics/css/`));

    let login  = ['css/home/index.css', 'css/home/login.reg.css'];
    let slogin = gulp.src(login, { cwd: `./${outsDir}/statics/` })
                     .pipe(order(sortMask(login)))
                     .pipe(concat('reg-login.css'))
                     .pipe(cleancss())
                     .pipe(rev())
                     .pipe(gulp.dest(`./${distDir}/statics/css/`));

    return es.merge([slogin, ss]);
});

gulp.task('cssmin', ['cssminHome']);

// ------------------------- copy files  ---------------------------------
gulp.task('copyImg', ['clean'], function() {
    return gulp.src(['src/statics/img/**/*'])
               .pipe(gulp.dest(`./${distDir}/statics/img/`));
});

gulp.task('copyRes', ['clean'], function() {
    return gulp.src(['src/statics/res/**/*'])
               .pipe(gulp.dest(`./${distDir}/statics/res/`));
});

gulp.task('copy', ['copyImg', 'copyRes']);

// ------------------------- html minify ---------------------------------
var injects = {
    styles : [
        { name: 'home_css', src: 'css/home-*.css' },
        { name: 'admin_css', src: 'css/admin-*.css' },
        { name: 'common_css', src: 'css/common-*.css' },
        { name: 'reg-login_css', src: 'css/reg-login-*.css' },
    ],
    scripts: [
        { name: 'jquery', src: 'js/jquery.min.js' },
        { name: 'angular', src: 'js/angular.min.js' },
        { name: 'common_js', src: 'js/common-*.js' },
        { name: 'home_js', src: 'js/home-*.js' },
        { name: 'home3rd_js', src: 'js/home3rd-*.js' },
        { name: 'admin_js', src: 'js/admin-*.js' },
        { name: 'ueditor_js', src: 'js/ueditor.js' },
        { name: 'admin3rd_js', src: 'js/admin3rd-*.js' },
        { name: 'admin-login_js', src: 'js/adminlogin-*.js' },
    ]
};
gulp.task('inject', ['copy', 'cssmin', 'tsc'], function() {
    let minOpt = { minifyJS: true, minfiyCSS: true, collapseWhitespace: true };
    let src    = ['src/**/*.html', '!src/statics/res/**/*.html'];

    let shtml = gulp.src(src);
    injects.styles.forEach((item) => {
        let opt = {
            name        : item.name,
            quiet       : true,
            removeTags  : true,
            addRootSlash: !isProdBuild,
            ignorePath  : `${distDir}/statics`
        };
        shtml   = shtml.pipe(inject(gulp.src(`./${distDir}/statics/${item.src}`, { read: false }), opt));
    });
    injects.scripts.forEach((item) => {
        let opt = {
            name        : item.name,
            quiet       : true,
            removeTags  : true,
            addRootSlash: !isProdBuild,
            ignorePath  : `${distDir}/statics`
        };
        shtml   = shtml.pipe(inject(gulp.src(`./${distDir}/statics/${item.src}`, { read: false }), opt));
    });
    return shtml.pipe(htmlmin(minOpt)).pipe(gulp.dest(`./${distDir}`));
});

gulp.task('htmlmin', ['inject'], function() {
    let src = [
        `./${distDir}/**/*.html`,
        `!./${distDir}/statics/res/**/*.html`,
        `!./${distDir}/statics/vendor/**/*.html`,
    ];
    return gulp.src(src)
               .pipe(htmlmin(htmlCfg))
               .pipe(gulp.dest(`./${distDir}`));
});

// ------------------------------ build ----------------------------------
let depends = [
    'appmin',
    'cssmin',
    'copy',
    'htmlmin',
];
gulp.task('build', depends, () => {
    return gulp.src('package.json')
               .pipe(change(function(text, done) {
                   let pack = JSON.parse(text);
                   delete pack.devDependencies;
                   delete pack.scripts;
                   text = JSON.stringify(pack);
                   done(false, text);
               }))
               .pipe(gulp.dest(`./${distDir}`));
});

// --------------------------- deploy steps ------------------------------
gulp.task('pull', ['clean'], function() {
    Promise.resolve().then(()=> {
        let key = 'test';
        let cmd = `eval \`ssh-agent -s\` > /dev/null;ssh-add ~/.ssh/${deployRepos[key].key} > /dev/null;`
                  + `git pull ${key} master --tags; ssh-agent -k > /dev/null`;
        return new Promise((resolve, reject) => {
            spawn(cmd, { cwd: distDir }, (err, stdout, stderr) => {
                if( err ) {
                    return reject(err);
                }
                console.log(stderr);
                console.log(stdout);
                resolve();
            });
        });
    }).catch((err) => {
        console.error(err);
    });
});

gulp.task('dist-init', ['clean'], function() {
    mkdirp.sync(distDir);
    gitExec('init', distDir, true).then(() => {
        let repos = [];
        for( let k in deployRepos ) {
            repos.push(gitExec(`remote add ${k} ${deployRepos[k].src}`, distDir, true));
        }
        return Promise.all(repos);
    }).then(()=> {
        return gitExec('remote -v', distDir, true);
    }).then((result)=> {
        console.log(result);
    }).catch((err) => {
        console.error(err);
    });
});

gulp.task('version', ['build'], () => {
    let msg = "v0.0.1";
    let ver = 'v0.0.1';
    gitExec('log -n 1 --pretty=format:%B', distDir, true).then((result)=> {
        result    = result.replace('\n', '');
        let vcode = result.match(/\d+$/);
        if( vcode && vcode[0] ) {
            vcode = parseInt(vcode[0]) + 1;
            msg   = result.replace(/\d+$/, vcode);
        }
        ver = msg.replace('build ', '');
        fs.writeFileSync(`./${distDir}/build.txt`, `${ver}\n`);
        return gitExec('add .', distDir, true);
    }).then(() => {
        return gitExec(`commit -a -m "${msg}"`, distDir, true);
    }).then(() => {
        return gitExec('log -n 1 --pretty=format:%B', distDir, true).then((result) => {
            console.log('\ncurrent version:', result.replace('\n', ''));
        });
    }).then(() => {
        return gitExec(`tag ${ver}`, distDir, true);
    }).then(() => {
        let key = 'test';
        let cmd = `eval \`ssh-agent -s\` > /dev/null;ssh-add ~/.ssh/${deployRepos[key].key} > /dev/null;`
                  + `git push ${key} master --tag; ssh-agent -k > /dev/null`;
        return new Promise((resolve, reject) => {
            spawn(cmd, { cwd: distDir }, (err, stdout, stderr) => {
                if( err ) {
                    return reject(err);
                }
                console.log(stderr);
                console.log(stdout);
                resolve();
            });
        });
    }).catch((err) => {
        console.log(err);
    });
});

gulp.task('deploy', () => {
    let repo = process.argv[2];
    if( !deployRepos[repo] ) {
        return;
    }

    let cmd = `eval \`ssh-agent -s\` > /dev/null; `
              + `ssh-add ~/.ssh/${deployRepos[repo].key} > /dev/null; `
              + `git push ${repo} master --tag; ssh-agent -k > /dev/null`;
    return new Promise((resolve, reject) => {
        spawn(cmd, { cwd: distDir }, (err, stdout, stderr) => {
            if( err ) {
                return reject(err);
            }
            console.log(stderr);
            console.log(stdout);
            resolve();
        });
    });
});

gulp.task('test', ['version']);

gulp.task('hc360', ['deploy']);

gulp.task('aliyun', ['deploy']);

gulp.task('product', ['deploy']);

gulp.task('trainer', ['deploy']);

gulp.task('local-test', () => {
    if( isWinOS ) {
        console.log('local tester is only support running on linux!');
    } else {
        tester.start();
    }
});

// ----------------------------- watching --------------------------------
gulp.task('watch', ['tsc'], () => {
    watch(tsFiles, { read: false }, (evt) => {
        let src  = evt.path.replace(__dirname, '.').replace(/\\/g, '/');
        let dest = src.replace('/src/', `/${outsDir}/`).replace(/ts$/, 'js');
        if( evt.event == 'unlink' ) {
            console.log(` - Deleting ${dest}`);
            del([dest, dest + '.map']);
            return;
        }

        let finished = false;
        let files    = ['typings/index.d.ts', evt.path];

        console.log(` - Starting tsc ${src} => ${dest}`);
        gulp.src(files)
            .pipe(sourcemaps.init())
            .pipe(ts(tsConfig)).js
            .pipe(sourcemaps.write('.'))
            .pipe(replace('"use strict";', "'use strict';\nrequire('source-map-support/register');"))
            .pipe(gulp.dest(outsDir))
            .pipe(change((_, cb)=> {
                if( finished ) {
                    return;
                }
                finished = true;
                console.log(` - Finished tsc ${src} => ${dest}`);
                cb();
            }));
    });

    let statics = [];
    if( isWinOS ) {
        statics.push('./src/statics/');
        statics.push('!./src/statics/**/*.less');
        watch(statics, {
            events: ['add', 'change', 'unlink', 'addDir', 'unlinkDir'],
            read  : false
        }, function(evt) {
            if( !/.(html|png|jpg|gif|js|css|json)$/.test(evt.path) ) {
                return;
            }

            let src  = evt.path.replace(__dirname, '.').replace(/\\/g, '/');
            let dest = path.dirname(evt.path).replace('/src/', `/${outsDir}/`);
            if( evt.event == 'unlink' || evt.event == 'unlinkDir' ) {
                let buildPath = path.join(dest, path.basename(evt.path));
                console.log(` - Deleting ${buildPath}`);
                del([buildPath]);
                return;
            }

            console.log(
                'Copying',
                evt.path.replace(__dirname, '.').replace(/\\/g, '/'),
                '=>',
                src.replace('/src/', `/${outsDir}/`).replace(/less/, 'css')
            );
            gulp.src(evt.path).pipe(gulp.dest(dest));
        });

        server.start();
        gulp.watch(statics, (file) => {
            server.notify.bind(server)(file);
        });

        let appJs = [
            './actions/**/*.js',
            './models/**/*.js',
            './config/**/*.js',
            './common/**/*.js',
            './core/**/*.js',
            './*.js'
        ];
        gulp.watch(appJs, { cwd: outsDir }, () => {
            server.start.bind(server)();
        });

    } else {
        server.start();
        gulp.watch(statics, (file) => {
            server.notify.bind(server)(file);
        });

        let appJs = [
            './actions/**/*.js',
            './models/**/*.js',
            './config/**/*.js',
            './common/**/*.js',
            './core/**/*.js',
            './*.js'
        ];
        gulp.watch(appJs, { cwd: outsDir }, () => {
            server.start.bind(server)();
        });
    }
});

// ----------------------------- defatult --------------------------------
gulp.task('default', ['watch'], () => {
});
